home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / filesyst / ncpfs / ipxripd-.000 / ipxripd- / ipxripd / ipxsapd.c < prev    next >
C/C++ Source or Header  |  1996-02-03  |  15KB  |  741 lines

  1. /*
  2.    IPX service advertising daemon 
  3.  
  4.    Copyright (C) 1994, 1995  Ales Dryak <e-mail: A.Dryak@sh.cvut.cz>
  5.    Copyright (C) 1996, Volker Lendecke <lendecke@namu01.gwdg.de>
  6.  
  7.    This program is free software; you can redistribute it and/or modify
  8.    it under the terms of the GNU General Public License as published by
  9.    the Free Software Foundation; either version 2 of the License, or
  10.    (at your option) any later version.
  11.  
  12.    This program is distributed in the hope that it will be useful,
  13.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.    GNU General Public License for more details.
  16.  
  17.    You should have received a copy of the GNU General Public License
  18.    along with this program; if not, write to the Free Software
  19.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  
  21.  */
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <sys/types.h>
  25. #include <sys/time.h>
  26. #include <sys/ioctl.h>
  27. #include <unistd.h>
  28. #include <fcntl.h>
  29. #include <string.h>
  30. #include <signal.h>
  31. #include <errno.h>
  32. #include <sys/socket.h>
  33. #include <netinet/in.h>
  34. #include <linux/ipx.h>
  35. #include "ipxutil.h"
  36. #include "ipxkern.h"
  37. #include "ipxsap.h"
  38. #include "ipxd.h"
  39.  
  40. typedef unsigned char ifc_timer;
  41.  
  42. struct sap_table
  43. {
  44.     ser_name_t name;
  45.     ser_type_t type;
  46.     hop_t hops;
  47.     struct sockaddr_ipx addr;
  48.     ifc_timer timers[MAX_IFACE];    /* 0<timer<EXPIRE_TIME => net is
  49.                        reachable via iface */
  50.     /* timer>=EXPIPE_TIME => net is not r. */
  51.     struct sap_table *next;
  52. };
  53.  
  54. struct sap_table *stable = NULL;
  55.  
  56. static IPXNet
  57. ifc_net(struct ipx_interface *ifc)
  58. {
  59.     return ntohl(ifc->s_output.dest_addr.sipx_network);
  60. }
  61.  
  62. static int
  63. is_expired(ifc_timer * tm)
  64. {
  65.     return *tm >= EXPIRE_TIME;
  66. }
  67.  
  68. static void
  69. setup_timers(struct sap_table *rt, struct ipx_interface *ifc)
  70. {
  71.     ifc_timer *tm;
  72.  
  73.     for (tm = rt->timers; tm < rt->timers + MAX_IFACE; tm++)
  74.     {
  75.         *tm = EXPIRE_TIME;
  76.     }
  77.     rt->timers[ifc_get_index(ifc)] = 0;
  78. }
  79.  
  80. static void
  81. output_flushall()
  82. {
  83.     struct ipx_interface *ifc = first_interface();
  84.  
  85.     while (ifc != NULL)
  86.     {
  87.         ipx_sap_output_flush(&(ifc->s_output));
  88.         ifc = next_interface(ifc);
  89.     }
  90. }
  91.  
  92. static void
  93. output_set_destination(struct ipx_interface *ifc,
  94.                IPXNode dest_node, IPXPort dest_port)
  95. {
  96.     if (ifc == NULL)
  97.     {
  98.         ifc = first_interface();
  99.         while (ifc != NULL)
  100.         {
  101.             ipx_sap_output_set_destination(&(ifc->s_output),
  102.                                dest_node, dest_port);
  103.             ifc = next_interface(ifc);
  104.         }
  105.     }
  106.     else
  107.     {
  108.         ipx_sap_output_set_destination(&(ifc->s_output), dest_node,
  109.                            dest_port);
  110.     }
  111. }
  112.  
  113. static void
  114. output_broadcast(struct sap_table *st, int down_allow)
  115. {
  116.     struct ipx_interface *ifc = first_interface();
  117.  
  118.     while (ifc != NULL)
  119.     {
  120.         if (is_expired(&(st->timers[ifc_get_index(ifc)])))
  121.         {
  122.             ipx_sap_output_response(&(ifc->s_output),
  123.                         st->type, st->name,
  124.                         &(st->addr),
  125.                         st->hops, down_allow);
  126.         }
  127.         ifc = next_interface(ifc);
  128.     }
  129. }
  130.  
  131. static int
  132. output_sendto(int sock, void *buffer, int size, struct sockaddr_ipx *daddr)
  133. {
  134.     int res;
  135.  
  136.     DL_ENTRY;
  137.     DL_START;
  138.     fprintf(log_file, "Sending SAP to ");
  139.     ipx_fprint_saddr(log_file, daddr);
  140.     fprintf(log_file, "\n");
  141.     ipx_sap_fdump(log_file, buffer, size);
  142.     DL_END;
  143.  
  144.     res = sendto(sock, (void *) buffer, size, 0,
  145.              (struct sockaddr *) daddr, sizeof(*daddr));
  146.  
  147.     if (res == -1)
  148.     {
  149.         LOG_START;
  150.         fprintf(log_file, "sendto: %s\n", strerror(errno));
  151.         LOG_END;
  152.     }
  153.     return res;
  154. }
  155.  
  156. static void
  157. fprint_server(FILE * file, struct sap_table *st)
  158. {
  159.     int i;
  160.  
  161.     LOG_START;
  162.     fprintf(file, "SAP: type: %04X name: ", st->type);
  163.     ipx_sap_fprint_name(file, st->name);
  164.     fprintf(file, " \nhops: %i addr: ", st->hops);
  165.     ipx_fprint_saddr(file, &(st->addr));
  166.     fprintf(file, " ");
  167.  
  168.     for (i = first_ifc_index(); i >= 0; i = next_ifc_index(i))
  169.     {
  170.         ifc_timer *tm = &(st->timers[i]);
  171.         fprintf(file, "%i", is_expired(tm) ? 0 : 1);
  172.         DL_START;
  173.         fprintf(file, "(%i)", (int) *tm);
  174.         DL_END;
  175.     }
  176.     LOG_END;
  177. }
  178.  
  179. void
  180. fdump_servers(FILE * file)
  181. {
  182.     struct sap_table *st;
  183.  
  184.     LOG_START;
  185.     fprintf(file, "IPX server database:\n");
  186.     for (st = stable; st != NULL; st = st->next)
  187.     {
  188.         fprint_server(file, st);
  189.         fprintf(file, "\n");
  190.     }
  191.     LOG_END;
  192. }
  193.  
  194.  
  195. static struct sap_table *
  196. add_server(ser_type_t type, ser_name_t name, struct sockaddr_ipx *addr,
  197.        hop_t hops, struct ipx_interface *ifc)
  198. {
  199.     struct sap_table *st;
  200.  
  201.     st=(struct sap_table *)malloc(sizeof(struct sap_table));
  202.     if (st == NULL)
  203.     {
  204.         LOG_START;
  205.         fprintf(log_file, "ipxsapd: out of memory in add_server\n");
  206.         LOG_END;
  207.         return NULL;
  208.     }
  209.     st->type = type;
  210.     ipx_sap_assign_ser_name(st->name, name);
  211.     st->addr = *addr;
  212.     st->hops = hops;
  213.     setup_timers(st, ifc);
  214.     st->next = stable;
  215.     stable = st;
  216.     return st;
  217. }
  218.  
  219. static void
  220. delete_server(struct sap_table *d)
  221. {
  222.     struct sap_table **s;
  223.     for (s = &stable; *s != NULL; s = &((*s)->next))
  224.     {
  225.         if (*s == d)
  226.         {
  227.             *s = d->next;
  228.             free(d);
  229.             return;
  230.         }
  231.     }
  232. }
  233.  
  234. static void
  235. handle_sap_gns_request(struct sap_entry * se, struct ipx_interface *src_ifc)
  236. {
  237.     struct sap_table *cur;
  238.     struct sap_table *nearest = NULL;
  239.  
  240.     if (ntohs(se->ser_type) == IPX_SAP_GENERAL_RQ)
  241.     {
  242.         return;
  243.     }
  244.     for (cur = stable; cur != NULL; cur = cur->next)
  245.     {
  246.         if (ipx_sap_type_equal(cur->type, ntohs(se->ser_type)))
  247.         {
  248.             if (nearest == NULL)
  249.             {
  250.                 nearest = cur;
  251.                 continue;
  252.             }
  253.             if (nearest->hops > cur->hops)
  254.             {
  255.                 nearest = cur;
  256.             }
  257.         }
  258.     }
  259.     if (nearest != NULL)
  260.     {
  261.         ipx_sap_output_gns_response(&(src_ifc->s_output),
  262.                         nearest->type, nearest->name,
  263.                         &(nearest->addr), nearest->hops);
  264.     }
  265.     else
  266.     {
  267.         DL_START;
  268.         fprintf(log_file, "No servers of type %04X was found\n",
  269.             ntohs(se->ser_type));
  270.         DL_END;
  271.     }
  272. }
  273.  
  274. static void
  275. handle_sap_request(struct sap_entry *se, struct ipx_interface *src_ifc)
  276. {
  277.     struct sap_table *cur;
  278.     for (cur = stable; cur != NULL; cur = cur->next)
  279.     {
  280.         if (ipx_sap_type_equal(cur->type, ntohs(se->ser_type)))
  281.         {
  282.             ipx_sap_output_response(&(src_ifc->s_output),
  283.                         cur->type, cur->name,
  284.                         &(cur->addr), cur->hops, 0);
  285.         }
  286.     }
  287. }
  288.  
  289. static void
  290. handle_sap_response(struct sap_entry *se, struct ipx_interface *src_ifc,
  291.             IPXNode src_node)
  292. {
  293.     struct sap_table *cur;
  294.  
  295.     se->hops = ntohs(se->hops) + 1;
  296.  
  297.     for (cur = stable; cur != NULL; cur = cur->next)
  298.     {
  299.         if (   !ipx_sap_name_equal(cur->name, se->ser_name)
  300.             || !ipx_sap_type_equal(cur->type, ntohs(se->ser_type)))
  301.         {
  302.             /* Another server was announced */
  303.             continue;
  304.         }
  305.         /* entry found */
  306.         if (se->hops <= IPX_SAP_SERVER_DOWN)
  307.         {
  308.             /* server ok */
  309.             if (se->hops > cur->hops)
  310.             {
  311.                 return;
  312.             }
  313.             if (se->hops == cur->hops)
  314.             {
  315.                 /* server has equal 'route' */
  316.                 /* update info (near not neccesary) */
  317.                 cur->addr.sipx_network = se->network;
  318.                 ipx_assign_node(cur->addr.sipx_node, se->node);
  319.                 cur->addr.sipx_port = se->port;
  320.  
  321.                 /* update timer for iface */
  322.                 cur->timers[ifc_get_index(src_ifc)] = 0;
  323.                 return;
  324.             }
  325.             /* server has better 'route' */
  326.             LOG_START;
  327.             fprintf(log_file, "CHANGE ");
  328.             fprint_server(log_file, cur);
  329.             LOG_END;
  330.  
  331.             /* update table */
  332.             cur->hops = se->hops;
  333.             cur->addr.sipx_network = se->network;
  334.             ipx_assign_node(cur->addr.sipx_node, se->node);
  335.             cur->addr.sipx_port = se->port;
  336.             setup_timers(cur, src_ifc);
  337.  
  338.             LOG_START;
  339.             fprintf(log_file, "\nto     ");
  340.             fprint_server(log_file, cur);
  341.             fprintf(log_file, "\n");
  342.             LOG_END;
  343.  
  344.             /* send info bcast */
  345.             output_broadcast(cur, 0);
  346.         }
  347.         else
  348.         {
  349.             /* server down through iface */
  350.             int src_ifc_idx = ifc_get_index(src_ifc);
  351.             int i;
  352.  
  353.             cur->timers[src_ifc_idx] = EXPIRE_TIME;
  354.             for (i = first_ifc_index();
  355.                  i >= 0;
  356.                  i = next_ifc_index(i))
  357.             {
  358.                 ifc_timer *tm = &(cur->timers[i]);
  359.                 if (!is_expired(tm))
  360.                 {
  361.                     return;
  362.                 }
  363.             }
  364.             cur->timers[src_ifc_idx] = 0;
  365.             cur->hops = IPX_SAP_SERVER_DOWN;
  366.             output_broadcast(cur, 1);
  367.             LOG_START;
  368.             fprintf(log_file, "DELETE ");
  369.             fprint_server(log_file, cur);
  370.             fprintf(log_file, " (service down)\n");
  371.             LOG_END;
  372.             delete_server(cur);
  373.         }
  374.         return;
  375.     }
  376.     /* entry not found */
  377.     if (se->hops <= IPX_SAP_SERVER_DOWN)
  378.     {
  379.         struct sap_table *st;
  380.         struct sockaddr_ipx addr;
  381.  
  382.         addr.sipx_network = se->network;
  383.         ipx_assign_node(addr.sipx_node, se->node);
  384.         addr.sipx_port = se->port;
  385.         addr.sipx_type = 0;
  386.         st = add_server(ntohs(se->ser_type), se->ser_name, &addr,
  387.                 se->hops, src_ifc);
  388.         if (st != NULL)
  389.         {
  390.             /* send info bcast */
  391.             output_broadcast(st, 0);
  392.             LOG_START;
  393.             fprintf(log_file, "ADD ");
  394.             fprint_server(log_file, st);
  395.             fprintf(log_file, "\n");
  396.             LOG_END;
  397.         }
  398.     }
  399. }
  400.  
  401. void
  402. handle_sap(struct sap_packet * pkt, int len, struct sockaddr_ipx *sipx,
  403.        struct ipx_interface *src_ifc)
  404. {
  405.     struct sap_entry *se = pkt->sap_entries;
  406.     int nent = (len - 2) / sizeof(struct sap_entry);
  407.  
  408.     if (ifc_net(src_ifc) != ntohl(sipx->sipx_network))
  409.     {
  410.         LOG_START;
  411.         fprintf(log_file, "SAP from non-local net ");
  412.         ipx_fprint_network(log_file, ntohl(sipx->sipx_network));
  413.         fprintf(log_file, " (ignored)\n");
  414.         LOG_END;
  415.         return;
  416.     }
  417.     if (len < 2)
  418.     {
  419.         LOG_START;
  420.         fprintf(log_file,
  421.             "SAP packet too small len=%i (ignored)\n", len);
  422.         LOG_END;
  423.         return;
  424.     }
  425.     if (   ipx_node_equal(src_ifc->ifnode, sipx->sipx_node)
  426.         && ((unsigned short) sipx->sipx_port == htons(IPX_SAP_PORT)))
  427.     {
  428.         DL_START;
  429.         fprintf(log_file, "My packet (ignored)\n");
  430.         DL_END;
  431.         return;
  432.     }
  433.     switch (ntohs(pkt->operation))
  434.     {
  435.     case IPX_SAP_OP_REQUEST:
  436.         if (len != ipx_sap_size(1, IPX_SAP_OP_REQUEST))
  437.         {
  438.             LOG_START;
  439.             fprintf(log_file,
  440.                 "SAP packet invalid size (ignored)\n");
  441.             LOG_END;
  442.             return;
  443.         }
  444.         output_set_destination(src_ifc,
  445.                        sipx->sipx_node,
  446.                        ntohs(sipx->sipx_port));
  447.         handle_sap_request(se, src_ifc);
  448.         output_flushall();
  449.         break;
  450.     case IPX_SAP_OP_GNS_REQUEST:
  451.         if (len != ipx_sap_size(1, IPX_SAP_OP_REQUEST))
  452.         {
  453.             LOG_START;
  454.             fprintf(log_file,
  455.                 "SAP packet invalid size (ignored)\n");
  456.             LOG_END;
  457.             return;
  458.         }
  459.         output_set_destination(src_ifc,
  460.                        sipx->sipx_node,
  461.                        ntohs(sipx->sipx_port));
  462.         handle_sap_gns_request(se, src_ifc);
  463.         output_flushall();
  464.         break;
  465.     case IPX_SAP_OP_RESPONSE:
  466.         /* option: ignore responses from non SAP ports
  467.          * if (sipx->sipx_port!=htons(IPX_SAP_PORT)) return;
  468.          */
  469.         output_set_destination(NULL, IPX_BROADCAST, IPX_SAP_PORT);
  470.         for (; nent--; se++)
  471.         {
  472.             handle_sap_response(se, src_ifc, sipx->sipx_node);
  473.         }
  474.         output_flushall();
  475.         break;
  476.     case IPX_SAP_OP_GNS_RESPONSE:
  477.         LOG_START;
  478.         fprintf(log_file,
  479.             "GNS response should never be received (ignored)\n");
  480.         LOG_END;
  481.         break;
  482.     default:
  483.         LOG_START;
  484.         fprintf(log_file, "Unknown SAP operation\n");
  485.         LOG_END;
  486.         break;
  487.     }
  488. }
  489.  
  490. static void
  491. delete_invalid_servers(void)
  492. {
  493.     struct sap_table **s = &stable;
  494.  
  495.     while (*s != NULL)
  496.     {
  497.         if ((*s)->hops >= IPX_SAP_SERVER_DOWN)
  498.         {
  499.             struct sap_table *d = *s;
  500.             *s = (*s)->next;
  501.             free(d);
  502.         }
  503.         else
  504.         {
  505.             s = &((*s)->next);
  506.         }
  507.     }
  508. }
  509.  
  510. void
  511. ipx_sap_do_aging(int rate, int do_broadcast)
  512. {
  513.     struct sap_table *cur;
  514.     int servers_died = 0;
  515.  
  516.     DL_START;
  517.     fprintf(log_file, "DO SAP AGING\n");
  518.     DL_END;
  519.  
  520.     output_set_destination(NULL, IPX_BROADCAST, IPX_SAP_PORT);
  521.     for (cur = stable; cur != NULL; cur = cur->next)
  522.     {
  523.         int down = 1;
  524.         int i;
  525.  
  526.         for (i = first_ifc_index(); i >= 0; i = next_ifc_index(i))
  527.         {
  528.             ifc_timer* tm = &(cur->timers[i]);;
  529.             if (!is_expired(tm))
  530.             {
  531.                 (*tm) += rate;
  532.                 if (!is_expired(tm))
  533.                 {
  534.                     down = 0;
  535.                 }
  536.             }
  537.         }
  538.         if (down)
  539.         {
  540.             /* server is down */
  541.             LOG_START;
  542.             fprintf(log_file, "DELETE ");
  543.             fprint_server(log_file, cur);
  544.             fprintf(log_file, " (timed out)\n");
  545.             LOG_END;
  546.             /* send info bcast */
  547.             cur->hops = IPX_SAP_SERVER_DOWN;
  548.             output_broadcast(cur, 1);
  549.             /* table update deferred */
  550.             servers_died = 1;
  551.         } else
  552.         {
  553.             if (do_broadcast != 0)
  554.             {
  555.                 output_broadcast(cur, 0);
  556.             }
  557.         }
  558.     }
  559.  
  560.     /* Delete bogus servers */
  561.     if (servers_died != 0)
  562.     {
  563.         delete_invalid_servers();
  564.     }
  565.     output_flushall();
  566. }
  567.  
  568. int
  569. ipx_sap_init_ifc(struct ipx_interface *ifc, IPXNet network,
  570.          char *device, int type, void *data)
  571. {
  572.     struct sockaddr_ipx sipx;
  573.  
  574.     if (ipx_sap_output_init(&(ifc->s_output), network) != 0)
  575.     {
  576.         LOG_START;
  577.         fprintf(log_file,
  578.             "out of memory allocating output buffer\n");
  579.         LOG_END;
  580.         return -1;
  581.     }
  582.  
  583.     if ((ifc->s_output.sk = socket(AF_IPX, SOCK_DGRAM, PF_IPX)) < 0)
  584.     {
  585.         LOG_START;
  586.         fprintf(log_file,
  587.             "can't open socket: %s\n", strerror(errno));
  588.         LOG_END;
  589.         return -1;
  590.     }
  591.  
  592.     memset(&sipx, 0, sizeof(sipx));
  593.     sipx.sipx_family = AF_IPX;
  594.     sipx.sipx_network = htonl(network);
  595.     ipx_assign_node(sipx.sipx_node, IPX_THIS_NODE);
  596.     sipx.sipx_port = htons(IPX_SAP_PORT);
  597.     sipx.sipx_type = IPX_SAP_PTYPE;
  598.  
  599.     if (bind(ifc->s_output.sk, (struct sockaddr *)&sipx, sizeof(sipx)) < 0)
  600.     {
  601.         LOG_START;
  602.         fprintf(log_file, "can't bind socket: %s\n",
  603.             strerror(errno));
  604.         LOG_END;
  605.         return -1;
  606.     }
  607.  
  608.     if (ipx_kern_enable_broadcast(ifc->s_output.sk) != 0)
  609.     {
  610.         LOG_START;
  611.         fprintf(log_file, "cant' enable broadcast\n");
  612.         LOG_END;
  613.         exit(1);
  614.     }
  615.  
  616.     LOG_START;
  617.     ipx_fprint_network(log_file, network);
  618.     fprintf(log_file,":");
  619.     ipx_fprint_node(log_file, ifc->ifnode);
  620.     fprintf(log_file,"\n");
  621.     LOG_END;
  622.     return 0;
  623. }
  624.  
  625. static void
  626. ipx_sap_deinit_ifc(struct ipx_interface *ifc)
  627. {
  628.     close(ifc->s_output.sk);
  629. }
  630.  
  631. void
  632. ipx_sap_down_ifc(struct ipx_interface *ifc)
  633. {
  634.     struct sap_table *st;
  635.     int ifc_index = ifc_get_index(ifc);
  636.  
  637.     LOG_ENTRY;
  638.     LOG_START;
  639.     fprintf(log_file, "SAP DOWN INTERFACE ");
  640.     ipx_fprint_network(log_file, ifc_net(ifc));
  641.     fprintf(log_file, "\n");
  642.     LOG_END;
  643.  
  644.     for (st = stable; st != NULL; st = st->next)
  645.     {
  646.         int i;
  647.         st->timers[ifc_index] = EXPIRE_TIME;
  648.  
  649.         for (i = first_ifc_index();
  650.              i >= 0;
  651.              i = next_ifc_index(i))
  652.         {
  653.             ifc_timer *tm = &(st->timers[i]);
  654.             if (!is_expired(tm))
  655.             {
  656.                 return;
  657.             }
  658.         }
  659.         st->timers[ifc_index] = 0;
  660.         st->hops = IPX_SAP_SERVER_DOWN;
  661.         output_broadcast(st, 1);
  662.         LOG_START;
  663.         fprintf(log_file, "DELETE ");
  664.         fprint_server(log_file, st);
  665.         fprintf(log_file, " (interface down)\n");
  666.         LOG_END;
  667.     }
  668.  
  669.     delete_invalid_servers();
  670.     ipx_sap_deinit_ifc(ifc);
  671. }
  672.  
  673. void
  674. ipx_sap_initial_broadcasts()
  675. {
  676.     struct ipx_interface *ifc;
  677.     ipx_sap_output_func = output_sendto;
  678.     
  679.     output_set_destination(NULL, IPX_BROADCAST, IPX_SAP_PORT);
  680.  
  681.     ifc = first_interface();
  682.  
  683.     while (ifc != NULL)
  684.     {
  685.         ipx_sap_output_request(&(ifc->s_output),
  686.                        IPX_SAP_GENERAL_RQ);
  687.         ifc = next_interface(ifc);
  688.     }
  689.     output_flushall();
  690. }
  691.  
  692. void
  693. ipx_sap_initial_broadcast(struct ipx_interface *ifc)
  694. {
  695.     struct sap_table *cur;
  696.  
  697.     output_set_destination(ifc, IPX_BROADCAST, IPX_SAP_PORT);
  698.  
  699.     for (cur = stable; cur != NULL; cur = cur->next)
  700.     {
  701.         ipx_sap_output_response(&(ifc->s_output),
  702.                     cur->type, cur->name,
  703.                     &(cur->addr), cur->hops, 0);
  704.     }
  705.  
  706.     ipx_sap_output_request(&(ifc->s_output), IPX_SAP_GENERAL_RQ);
  707.     ipx_sap_output_flush(&(ifc->s_output));
  708. }
  709.  
  710. void
  711. ipx_sap_done()
  712. {
  713.     struct sap_table *cur;
  714.  
  715.     LOG_ENTRY;
  716.     LOG_START;
  717.     fprintf(log_file, "SAP Shutdown start\n");
  718.     LOG_END;
  719.  
  720.     output_set_destination(NULL, IPX_BROADCAST, IPX_SAP_PORT);
  721.     for (cur = stable; cur != NULL; cur = cur->next)
  722.     {
  723.         LOG_START;
  724.         fprintf(log_file, "DELETE ");
  725.         fprint_server(log_file, cur);
  726.         fprintf(log_file, " (shutdown)\n");
  727.         LOG_END;
  728.         /* send info bcast */
  729.         cur->hops = IPX_SAP_SERVER_DOWN;
  730.         output_broadcast(cur, 1);
  731.         /* update table */
  732.         delete_server(cur);
  733.     }
  734.     output_flushall();
  735.  
  736.     LOG_ENTRY;
  737.     LOG_START;
  738.     fprintf(log_file, "SAP Shutdown end\n");
  739.     LOG_END;
  740. }
  741.